/**
 * 微信支付v3版本
 * 请勿修改此处代码，因为插件更新后此处代码会被覆盖。
 * 作者：VK
 * 发布于：2021-07-06
 */
const fs = require("fs");
const crypto = require("crypto");
const libs = require('../../libs');

class VkWxPayV3 {

	constructor(config) {

		let {
			appId,
			secret,
			mchId, // 商户id
			v3Key, // v3密钥
			// 证书（路径形式）
			appCertPath,
			appPrivateKeyPath,
			// 证书（内容形式）路径和内容二选一即可
			appCertContent,
			appPrivateKeyContent,
			// 序列号形式
			appCertSn,
			authType = "WECHATPAY2-SHA256-RSA2048",
			// 微信平台证书
			wxpayPublicCertSn,
			wxpayPublicCertContent,
			apiUrl = "https://api.mch.weixin.qq.com",
			// 兼容老版本
			apiV3key,
			privateKey
		} = config;
		if (!v3Key && apiV3key) v3Key = apiV3key;
		if (!appPrivateKeyContent && privateKey) appPrivateKeyContent = privateKey;

		if (!appCertContent && appCertPath) {
			appCertContent = fs.readFileSync(appCertPath);
		}
		if (!appPrivateKeyContent && appPrivateKeyPath) {
			appPrivateKeyContent = fs.readFileSync(appPrivateKeyPath);
		}
		if (!appCertSn && appCertContent) {
			appCertSn = libs.certutil.wxpay.getSN(appCertContent);
		}

		this.config = {
			appId,
			secret,
			mchId,
			v3Key,
			appCertContent,
			appPrivateKeyContent,
			appCertSn,
			authType,
			wxpayPublicCertSn,
			wxpayPublicCertContent,
			apiUrl
		};
	}
	/**
	 * 构建请求签名参数
	 * @param method Http 请求方式
	 * @param url 请求接口 例如/v3/certificates
	 * @param timestamp 获取发起请求时的系统当前时间戳
	 * @param nonceStr 随机字符串
	 * @param body 请求报文主体
	 */
	sign(method, nonce_str, timestamp, url, body) {
		let str = method + '\n' + url + '\n' + timestamp + '\n' + nonce_str + '\n';
		if (body && body instanceof Object)
			body = JSON.stringify(body);
		if (body)
			str = str + body + '\n';
		if (method === 'GET')
			str = str + '\n';

		return this.sha256WithRsa(str);
	}
	/**
	 * SHA256withRSA
	 * @param data 待加密字符
	 */
	sha256WithRsa(data) {
		if (!this.config.appPrivateKeyContent) {
			throw new Error('缺少私钥证书');
		}
		return crypto.createSign('RSA-SHA256').update(data).sign(this.config.appPrivateKeyContent, 'base64');
	}
	/**
	 * 获取请求头授权字符串
	 * @param {Object} nonce_str
	 * @param {Object} timestamp
	 * @param {Object} signature
	 */
	getAuthorization(nonce_str, timestamp, signature) {
		let _authorization = 'mchid="' +
			this.config.mchId +
			'",' +
			'nonce_str="' +
			nonce_str +
			'",' +
			'timestamp="' +
			timestamp +
			'",' +
			'serial_no="' +
			this.config.appCertSn +
			'",' +
			'signature="' +
			signature +
			'"';
		return this.config.authType.concat(' ').concat(_authorization);
	}
	/**
	 * 验证签名，提醒：node 取头部信息时需要用小写，例如：req.headers['wechatpay-timestamp']
	 * @param params.timestamp HTTP头Wechatpay-Timestamp 中的应答时间戳
	 * @param params.nonce HTTP头Wechatpay-Nonce 中的应答随机串
	 * @param params.body 应答主体（response Body），需要按照接口返回的顺序进行验签，错误的顺序将导致验签失败。
	 * @param params.serial HTTP头Wechatpay-Serial 证书序列号
	 * @param params.signature HTTP头Wechatpay-Signature 签名
	 * @param params.apiSecret APIv3密钥，如果在 构造器 中有初始化该值(this.key)，则可以不传入。当然传入也可以
	 */
	verifySign(params) {
		let { timestamp, nonce, body, serial, signature, apiSecret } = params;
		let publicKey = this.config.wxpayPublicCertContent;
		let bodyStr = typeof body === 'string' ? body : JSON.stringify(body);
		let data = "".concat(timestamp, "\n").concat(nonce, "\n").concat(bodyStr, "\n");
		return crypto.createVerify('RSA-SHA256').update(data).verify(publicKey, signature, 'base64');
	}
	/**
	 * 回调解密
	 * @param ciphertext  Base64编码后的开启/停用结果数据密文
	 * @param associated_data 附加数据
	 * @param nonce 加密使用的随机串
	 * @param key  APIv3密钥
	 */
	decipherGcm(ciphertext, associated_data, nonce, key) {
		if (!key) key = this.config.v3Key;
		if (!key) throw new Error('缺少v3密钥key');
		let _ciphertext = Buffer.from(ciphertext, 'base64');
		// 解密 ciphertext字符  AEAD_AES_256_GCM算法
		let authTag = _ciphertext.slice(_ciphertext.length - 16);
		let data = _ciphertext.slice(0, _ciphertext.length - 16);
		let decipher = crypto.createDecipheriv('aes-256-gcm', key, nonce);
		decipher.setAuthTag(authTag);
		decipher.setAAD(Buffer.from(associated_data));
		let decoded = decipher.update(data, undefined, 'utf8');
		decipher.final();
		try {
			return JSON.parse(decoded);
		} catch (e) {
			return decoded;
		}
	}

	/**
	 * 敏感信息加密函数（使用微信平台公钥证书）
	 * @param text 待加密字符
	 */
	publicEncrypt(plaintext) {
		let publicKey = this.config.wxpayPublicCertContent; // 公钥内容
		let _plaintext = Buffer.from(plaintext, 'base64');
		let ciphertext = crypto.publicEncrypt({
				key: publicKey,
				padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
			},
			_plaintext
		);
		return ciphertext.toString("base64");
	}

	/**
	 * 阿里云http代理模式
	 */
	async httpProxyForEip(obj = {}) {
		let originalRes = await uniCloud.httpProxyForEip.postJson(
			obj.url,
			obj.data,
			obj.headers
		);

		try {
			if (typeof originalRes !== "object") {
				originalRes = JSON.parse(originalRes);
			}
			// 对齐差异
			originalRes.data = originalRes.body;
			originalRes.status = originalRes.statusCodeValue;
			if (!obj.needOriginalRes && originalRes && originalRes.data) {
				return originalRes.data;
			} else {
				return originalRes;
			}
		} catch (err) {
			return originalRes;
		}
	};

	async request(obj = {}) {
		let {
			method,
			url,
			data,
			needWechatpaySerial,
			needProxy,
			successText
		} = obj;

		method = method.toUpperCase();
		let timestamp = parseInt(Date.now() / 1000);
		let nonce_str = libs.common.random(32);
		let signature = this.sign(method, nonce_str, timestamp, url, data);
		let authorization = this.getAuthorization(nonce_str, timestamp, signature);
		let headers = {
			"Content-Type": "application/json",
			"Authorization": authorization,
		};
		if (needWechatpaySerial) {
			headers["Wechatpay-Serial"] = this.config.wxpayPublicCertSn;
		}
		let apiUrl = this.config.apiUrl;
		let result;
		if (needProxy && (uniCloud.$provider === "aliyun" || uniCloud.provider === "aliyun")) {
			// 走代理
			try {
				result = await this.httpProxyForEip({
					url: apiUrl + url,
					method,
					headers,
					data
				});
			} catch (err) {
				if (err.message.indexOf("40") > -1) {
					return {
						code: 403,
						msg: "有错误请检查：1、是否配置了微信支付转账接口的IP白名单；2、微信支付商户内的余额是否充足（有的商户区分了运营账户和基本账户的，请确认运营账户内的余额是否充足）；3、请确认微信支付V3密钥和证书是否正确。4、如还有问题，请加Q群：22466457，包解决！",
						err
					};
				} else {
					return { code: -1, msg: err.message }
				}
			}
		} else {
			// 不走代理
			result = await libs.common.request({
				url: apiUrl + url,
				method,
				headers,
				data
			});
		}
		let res = { code: 0, msg: "" };
		if (!result.code && typeof result.status !== "number") {
			res.msg = successText;
		} else {
			res.code = result.code || result.status || -1;
			res.msg = result.message || "未知错误";
		}
		res.result = result;
		return res;
	};

	/**
	 * 商家转账到零钱
	 * 详细参数：https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_1.shtml
	 * @param {String} out_batch_no  商户系统内部的商家批次单号，要求此参数只能由数字、大小写字母组成，在商户系统内部唯一 示例值：plfk2020042013
	 * @param {String} batch_name 该笔批量转账的名称 示例值：2019年1月深圳分部报销单
	 * @param {String} batch_remark 转账说明，UTF8编码，最多允许32个字符 示例值：2019年1月深圳分部报销单
	 * @param {String} total_amount 转账金额单位为“分”。转账总金额必须与批次内所有明细转账金额之和保持一致，否则无法发起转账操作 示例值：4000000
	 * @param {String} total_num 一个转账批次单最多发起三千笔转账。转账总笔数必须与批次内所有明细之和保持一致，否则无法发起转账操作 示例值：200
	 * @param {Array} transfer_detail_list 发起批量转账的明细列表，最多三千笔
	 * transfer_detail_list 参数
	 * @param {String} out_detail_no 商户系统内部区分转账批次单下不同转账明细单的唯一标识，要求此参数只能由数字、大小写字母组成 示例值：x23zy545Bd5436
	 * @param {String} transfer_amount 转账金额单位为分 示例值：200000
	 * @param {String} transfer_remark 单条转账备注（微信用户会收到该备注），UTF8编码，最多允许32个字符 示例值：2020年4月报销
	 * @param {String} openid openid是微信用户在公众号appid下的唯一用户标识（appid不同，则获取到的openid就不同），可用于永久标记一个用户
	 * @param {String} user_name  明细转账金额 >= 2,000元，收款用户姓名必填；
	 */
	async transfer(data = {}) {
		let {
			appid,
			out_batch_no,
			batch_name,
			batch_remark,
			total_amount,
			total_num,
			transfer_detail_list
		} = data;
		if (!appid) {
			appid = this.config.appId;
		}
		let needWechatpaySerial = false;
		transfer_detail_list.map((item, index) => {
			if (item.transfer_amount >= 200000) {
				item.user_name = this.publicEncrypt(item.user_name);
				needWechatpaySerial = true;
			} else {
				delete item.user_name;
			}
		});
		let res = await this.request({
			url: `/v3/transfer/batches`,
			method: "POST",
			needWechatpaySerial,
			needProxy: true,
			data: {
				appid,
				out_batch_no,
				batch_name,
				batch_remark,
				total_amount,
				total_num,
				transfer_detail_list
			}
		});
		return res;
	}

	/**
	 * 拉取平台证书
	 */
	async fetchCertificates() {
		let res = await this.request({
			method: "GET",
			url: `/v3/certificates`,
			successText: "获取成功"
		});
		if (res.code === 0) {
			try {
				let result = res.result;
				let ciphertext = result.data[0].encrypt_certificate.ciphertext;
				let associated_data = result.data[0].encrypt_certificate.associated_data;
				let nonce = result.data[0].encrypt_certificate.nonce;
				res.wxpayPublicCertSn = result.data[0].serial_no;
				res.wxpayPublicCertContent = this.decipherGcm(ciphertext, associated_data, nonce);
			} catch (err) {}
		}
		return res;
	}

}

module.exports = VkWxPayV3;
